<?php
declare (strict_types = 1);

namespace yifang\console;

use Closure;
use InvalidArgumentException;
use LogicException;

use yifang\console\command\Help;

use yifang\console\Input;
use yifang\console\input\Argument as InputArgument;
use yifang\console\input\Definition as InputDefinition;
use yifang\console\input\Option as InputOption;
use yifang\console\Output;

/**
 * 控制台应用管理类
 */
class Console
{
    protected $commands = [];

    protected $wantHelps = false;

    protected $catchExceptions = true;
    protected $autoExit        = true;
    protected $definition;
    protected $defaultCommand  = 'list';

    protected $defaultCommands = [
        'help' => Help::class,
    ];

    /**
     * 启动器
     * @var array
     */
    protected static $startCallbacks = [];

    public function __construct()
    {
        $this->initialize();
        $this->definition = $this->getDefaultInputDefinition();
        //加载指令
        $this->loadCommands();
        //启动
        $this->start();
    }

    /**
     * 启动
     */
    protected function start()
    {
        if(static::$startCallbacks){
            foreach (static::$startCallbacks as $callback) {
                $callback($this);
            }
        }
    }

    /**
     * 添加初始化器
     */
    public static function starting(Closure $callback)
    {
        static::$startCallbacks[] = $callback;
    }

    /**
     * 清空启动器
     */
    public static function flushStartCallbacks()
    {
        static::$startCallbacks = [];
    }

    /**
     * 加载指令
     */
    protected function loadCommands()
    {
        $commands = config('console.commands', []);
        $commands = array_merge($this->defaultCommands, $commands);
        $this->addCommands($commands);
    }

    /**
     * 添加指令集
     */
    public function addCommands(array $commands)
    {
        foreach ($commands as $key => $command) {
            if (is_subclass_of($command, Command::class)) {
                // 注册指令
                $this->addCommand($command, is_numeric($key) ? '' : $key);
            }
        }
    }

    /**
     * 添加一个指令
     */
    public function addCommand($command, $name = '')
    {
        if ($name) {
            $this->commands[$name] = $command;
        }
    }

    /**
     * 某个指令是否存在
     * @access public
     * @param string $name 指令名称
     * @return bool
     */
    public function hasCommand($name)
    {
        return isset($this->commands[$name]);
    }

    /**
     * 获取默认输入定义
     */
    protected function getDefaultInputDefinition()
    {
        return new InputDefinition([
            new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
            new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'),
        ]);
    }

    /**
     * 查找指令
     */
    public function find($name)
    {
         return $this->getCommand($name);
    }

    /**
     * 获取指令
     */
    public function getCommand($name)
    {
        if (!isset($this->commands[$name])) {
            throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name));
        }
        $command = $this->commands[$name];
        if (is_string($command)) {
            $command = app($command);
            $command->setConsole($this);
        }
        if ($this->wantHelps) {
            $this->wantHelps = false;
            $helpCommand = $this->getCommand('help');
            $helpCommand->setCommand($command);
            return $helpCommand;
        }
        return $command;
    }

    /**
     * 初始化
     */
    protected function initialize()
    {
        $this->makeRequest();
    }

    /**
     * 构造request
     */
    protected function makeRequest()
    {
        $url = config('app.url', 'http://localhost');
        $components = parse_url($url);
        $server = $_SERVER;
        if (isset($components['path'])) {
            $server = array_merge($server, [
                'SCRIPT_FILENAME' => $components['path'],
                'SCRIPT_NAME'     => $components['path'],
                'REQUEST_URI'     => $components['path'],
            ]);
        }
        if (isset($components['host'])) {
            $server['SERVER_NAME'] = $components['host'];
            $server['HTTP_HOST']   = $components['host'];
        }
        if (isset($components['scheme'])) {
            if ('https' === $components['scheme']) {
                $server['HTTPS']       = 'on';
                $server['SERVER_PORT'] = 443;
            } else {
                unset($server['HTTPS']);
                $server['SERVER_PORT'] = 80;
            }
        }
        if (isset($components['port'])) {
            $server['SERVER_PORT'] = $components['port'];
            $server['HTTP_HOST'] .= ':' . $components['port'];
        }
        request()->withServer($server);
    }

    /**
     * 设置输入参数定义
     */
    public function setDefinition(InputDefinition $definition)
    {
        $this->definition = $definition;
    }

    /**
     * 获取输入参数定义
     */
    public function getDefinition()
    {
        return $this->definition;
    }

    /**
     * 执行当前的指令
     */
    public function run()
    {
        $input  = new Input();
        $output = new Output();

        try {
            $exitCode = $this->doRun($input, $output);
        } catch (\Throwable $e) {
            if (!$this->catchExceptions) {
                throw $e;
            }
            $exitCode = $e->getCode();
            $exitMessage = $e->getMessage();
            if (is_numeric($exitCode)) {
                $exitCode = (int) $exitCode;
                if (0 === $exitCode) {
                    $exitCode = 1;
                }
            } else {
                $exitCode = 1;
            }
            echo $exitMessage;
        }
        if ($this->autoExit) {
            if ($exitCode > 255) {
                $exitCode = 255;
            }
            exit($exitCode);
        }
        return $exitCode;
    }

    /**
     * 执行指令
     */
    public function doRun(Input $input, Output $output)
    {
        $name = $this->getCommandName($input);
        if (true === $input->hasParameterOption(['--help', '-h'])) {
            if (!$name) {
                $name  = 'help';
                $input = new Input(['help']);
            } else {
                $this->wantHelps = true;
            }
        }
        if (!$name) {
            $name  = $this->defaultCommand;
            $input = new Input([$this->defaultCommand]);
        }
        $command = $this->find($name);
        return $this->doRunCommand($command, $input, $output);
    }

    /**
     * 执行指令
     */
    protected function doRunCommand(Command $command, Input $input, Output $output)
    {
        return $command->run($input, $output);
    }

    /**
     * 获取指令的基础名称
     */
    protected function getCommandName(Input $input)
    {
        return $input->getFirstArgument() ?: '';
    }

}
